package org.admin.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import core.data.DataHandle;
import org.admin.entity.domain.User;
import org.admin.entity.dto.FileDto;
import org.admin.entity.vo.FileFetchVo;
import org.admin.mapper.FileMapper;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.*;

@Service
public class FileService {
    @Autowired
    private FileMapper mapper;
    public final String UPLOAD_PATH = System.getProperty("user.home") + File.separator + "org.project"+ File.separator + "upload";
    public void image(DataHandle<FileDto, Object> handle, User user) {
        FileDto params = handle.getParams();
        BufferedImage origin;
        byte[] bytes;
        try {
            bytes = params.getFile().getBytes();
            origin = ImageIO.read(new ByteArrayInputStream(bytes));
        }catch (IOException e){
            handle.failure("file", e.getMessage());
            return ;
        }

        String signature = getSignature(bytes, params);
        QueryWrapper<org.admin.entity.domain.File> query = new QueryWrapper<>();
        org.admin.entity.domain.File file = mapper.selectOne(query.eq("signature", signature));
        if(file != null){
            // 这里有问题如果已知图片上传者与当前用户不是一个用户则继续生成仍然造成冗余
            if(Objects.equals(file.getUserId(), user.getId())) {
                handle.success(file.getPath());
                return;
            }
        }

        // 旋转图像
        origin = rotateImage(origin, params.getRotate(), params.getFlip());
        // 裁切图像
        origin = cropImage(origin, params.getTop(), params.getLeft(), params.getBottom(), params.getRight());
        // 缩放图像
        origin = resizeImage(origin, params.getWidth(), params.getHeight(), params.getCenter());
        BufferedImage icon = resizeImage(origin, 100, 100, true);

        String format = getFormat(params.getFile());
        format = format == null ? "png" : format;
        String name = getFileName(params.getFile());
        String path = getImagePath(user);

        Date now = new Date();
        if (!checkPath(path)) {
            handle.failure("创建目录失败或权限不足");
            return;
        }
        int width = origin.getWidth();
        int height = origin.getHeight();
        String fileName = UUID.randomUUID() + "_" + width + "x" + height + "." + format;
        path = path + File.separator + fileName;
        File outPut = new File(path);
        if(params.getQuality() > 0) {
            // 压缩图像
            Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
            ImageWriter writer = writers.next();
            try (ImageOutputStream ios = ImageIO.createImageOutputStream(new FileOutputStream(outPut))) {
                writer.setOutput(ios);
                ImageWriteParam param = writer.getDefaultWriteParam();
                param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                param.setCompressionQuality((float) params.getQuality());
                writer.write(null, new IIOImage(origin, null, null), param);
                writer.dispose();
            } catch (IOException e) {
                handle.failure("file", e.getMessage());
                writer.dispose();
                return;
            }

        }else{
            try {
                ImageIO.write(origin, format, outPut);
            } catch (IOException e) {
                handle.failure(e.getMessage());
                return;
            }
        }

        String iconPath = getIconPath(user);
        if (!checkPath(iconPath)) {
            handle.failure("创建目录失败或权限不足");
            return;
        }
        iconPath = iconPath + File.separator + fileName;
        File iconOutPut = new File(iconPath);
        try{
            ImageIO.write(icon, format, iconOutPut);
        } catch (IOException e) {
            handle.failure(e.getMessage());
            return ;
        }

        long size = 0;

        try {
            size = Files.size(Paths.get(path)) / 1024;
        } catch (IOException ignored) {}

        // 写入数据库
        org.admin.entity.domain.File fileDo = new org.admin.entity.domain.File();
        String outPath = path.substring(UPLOAD_PATH.length()).replaceAll("\\\\", "/");
        fileDo.setPath(outPath);
        fileDo.setUserId(user.getId());
        fileDo.setName(name);
        fileDo.setSize((int) size);
        fileDo.setCreatedAt(now);
        fileDo.setUpdateAt(now);
        fileDo.setTypeWith(org.admin.entity.domain.File.TYPE.IMAGE);
        fileDo.setFormat(format);
        fileDo.setSignature(signature);
        fileDo.setContentType(params.getFile().getContentType());
        mapper.insert(fileDo);
        handle.success(outPath);
    }

    public void upload(DataHandle<FileDto, Object> handle, User user) {
        FileDto params = handle.getParams();
        // 格式校验 、 安全校验
        // MultipartFile target = params.getFile();
        byte[] bytes;
        String base64;
        try {
            bytes = params.getFile().getBytes();
            base64 = Base64.getEncoder().encodeToString(bytes);
        }catch (IOException e){
            handle.failure(e.getMessage());
            return ;
        }

        String signature = getSignature(bytes, params);
        QueryWrapper<org.admin.entity.domain.File> query = new QueryWrapper<>();
        org.admin.entity.domain.File file = mapper.selectOne(query.eq("signature", signature));
        if(file != null){
            // 这里有问题如果已知文件上传者与当前用户不是一个用户则继续生成仍然造成冗余
            if(Objects.equals(file.getUserId(), user.getId())) {
                handle.success(file.getPath());
                return;
            }
        }


        String path = getFilePath(user);
        if(!checkPath(path)){
            handle.failure("创建目录失败或权限不足");
            return;
        }
        String name = getFileName(params.getFile());
        String format = getFormat(params.getFile());
        path = path + File.separator + UUID.randomUUID();
        File outPut = new File(path);
        try(FileWriter writer = new FileWriter(outPut, StandardCharsets.UTF_8)){
            writer.write(base64);
        }catch (IOException e){
            handle.failure(e.getMessage());
            return;
        }
        Date now = new Date();
        // 写入数据库
        org.admin.entity.domain.File fileDo = new org.admin.entity.domain.File();
        String outPath = path.substring(UPLOAD_PATH.length());
        fileDo.setPath(outPath);
        fileDo.setUserId(user.getId());
        fileDo.setName(name);
        fileDo.setSize((int) (params.getFile().getSize() / 1024));
        fileDo.setCreatedAt(now);
        fileDo.setUpdateAt(now);
        fileDo.setTypeWith(org.admin.entity.domain.File.TYPE.FILE);
        fileDo.setFormat(format);
        fileDo.setSignature(signature);
        fileDo.setContentType(params.getFile().getContentType());
        mapper.insert(fileDo);
        handle.success(outPath);

    }

    private BufferedImage rotateImage(BufferedImage origin, int count, int flip){
        BufferedImage image = origin;

        int type = origin.getType();
        // 图像通道
        if (type == BufferedImage.TYPE_CUSTOM) {
            type = BufferedImage.TYPE_INT_ARGB;
        }

        int width = origin.getWidth();
        int height = origin.getHeight();

        if(flip > 0){
            image = new BufferedImage(width, height, type);
            Graphics2D graph2d = image.createGraphics();
            AffineTransform at = new AffineTransform();
            int sx = 1;
            int sy = 1;
            if(flip % 2 == 0){
                at.scale(1, -1);
                at.translate(0, -height);

            }else{
                at.scale(-1, 1);
                at.translate(-width, 0);
            }
            graph2d.setTransform(at);
            graph2d.drawImage(origin, 0, 0, null);
            graph2d.dispose();
            origin = image;
        }
        if(count != 0){
            int nWidth = width;
            int nHeight = height;
            // 计算方向
            if(!(count % 2 == 0)){
                nWidth = height;
                nHeight = width;
            }
            image = new BufferedImage(nWidth, nHeight, type);
            Graphics2D graph2d = image.createGraphics();
            AffineTransform at = new AffineTransform();
            at.rotate(Math.toRadians(count * 90), (double) nWidth / 2, (double) nHeight / 2);
            graph2d.setTransform(at);
            graph2d.drawImage(origin, 0, 0, null);
            graph2d.dispose();
        }
        return image;
    }

    private BufferedImage cropImage(BufferedImage origin, int top, int left, int bottom, int right){
        if(top <= 0 && left <= 0 && bottom <= 0 && right <= 0)
            return origin;
        int width = origin.getWidth(null);
        int height = origin.getHeight(null);
        if(top <= 0)  top = 0;
        if(left <= 0) left = 0;

        width =  width - right - left;
        height =  height - bottom - top;

        return origin.getSubimage(left, top, width, height);
    }

    private BufferedImage resizeImage(BufferedImage origin, int resizeWidth, int resizeHeight, boolean center) {
        if(resizeWidth <= 0 && resizeHeight <= 0)
            return origin;
        int type = origin.getType();
        // 图像通道
        if (type == BufferedImage.TYPE_CUSTOM) {
            type = BufferedImage.TYPE_INT_ARGB;
        }
        int width = origin.getWidth(null);
        int height = origin.getHeight(null);
        int left = 0;
        int top = 0;

        if(resizeHeight <= 0){
            resizeHeight = (int) ((double) height * resizeWidth / width);
        }else if(resizeWidth <= 0){
            resizeWidth = (int) ((double) width * resizeHeight / height);
        }

        // 创建画布
        BufferedImage image = new BufferedImage(resizeWidth, resizeHeight, type);
        // 绘图工具
        Graphics2D graph2d = image.createGraphics();
        // RenderingHints
        // VALUE_INTERPOLATION_NEAREST_NEIGHBOR
        // VALUE_INTERPOLATION_BILINEAR
        // VALUE_INTERPOLATION_BICUBIC
        // 绘图模式
        graph2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);

        if (center) {
            if ((double) width / height > (double) resizeWidth / resizeHeight) {
                left = (width - (int) ((double)resizeWidth * height / resizeHeight)) / 2;
            } else if ((double) width / height < (double) resizeWidth / resizeHeight) {
                top = (height -  (int) ((double)width * resizeHeight / resizeWidth)) / 2;
            }
        }
        graph2d.drawImage(origin, 0, 0, resizeWidth, resizeHeight, left, top, width - left, height - top, null);
        graph2d.dispose();
        return image;
    }

    private String getFilePath(User user){
         return UPLOAD_PATH + File.separator
                + "file" + File.separator
                + "user_" + user.getId() + File.separator
                + new SimpleDateFormat("yyyy-MM-dd").format(new Date());
    }

    private String getImagePath(User user){
        return UPLOAD_PATH + File.separator
                + "image" + File.separator
                + "user_" + user.getId() + File.separator
                + new SimpleDateFormat("yyyy-MM-dd").format(new Date());
    }

    private String getIconPath(User user){
        return UPLOAD_PATH + File.separator
                + "icon" + File.separator
                + "user_" + user.getId() + File.separator
                + new SimpleDateFormat("yyyy-MM-dd").format(new Date());
    }

    private boolean checkPath(String path){
        File directory = new File(path);
        if(!directory.exists()){
            return directory.mkdirs();
        }
        return true;
    }

    private String getFileName(MultipartFile file){
        if(file == null || file.isEmpty()){
            return null;
        }
        String fileName = file.getOriginalFilename();
        if (fileName == null) {
            return null;
        }
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex == -1) {
            return fileName;
        }
        return fileName.substring(0, dotIndex);
    }

    private String getFormat(MultipartFile file){
        if(file == null || file.isEmpty()){
            return null;
        }
        String fileName = file.getOriginalFilename();
        if (fileName == null) {
            return null;
        }
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex == -1) {
            return null;
        }
        return fileName.substring(dotIndex + 1).toLowerCase();
    }

    public void query(DataHandle<FileDto, Object> handle, User user) {
        FileDto params = handle.getParams();

        MPJLambdaWrapper<org.admin.entity.domain.File> query = JoinWrappers.lambda(org.admin.entity.domain.File.class)
                .eq(org.admin.entity.domain.File::getUserId, user.getId());
        if(params.getType() != 0){
            query.eq(org.admin.entity.domain.File::getType, params.getType());
        }
        IPage<FileFetchVo> list = mapper.selectJoinPage(
                new Page<>(params.getCurrent(), params.getSize()),
                FileFetchVo.class,
                query);

        handle.success(list);
    }

    private String getSignature(byte[] bytes, FileDto dto){
        Map<String, String> map = new TreeMap<>();
        map.put("width", String.valueOf(dto.getWidth()));
        map.put("height", String.valueOf(dto.getHeight()));
        map.put("top", String.valueOf(dto.getTop()));
        map.put("left", String.valueOf(dto.getLeft()));
        map.put("right", String.valueOf(dto.getRight()));
        map.put("bottom", String.valueOf(dto.getBottom()));
        map.put("center", String.valueOf(dto.getCenter() ? 1 : 0));
        map.put("rotate", String.valueOf(dto.getRotate()));
        map.put("flip", String.valueOf(dto.getFlip()));
        map.put("quality", String.valueOf(dto.getQuality()));
        map.put("type", String.valueOf(dto.getType()));

        List<String> arrMap = new ArrayList<>();

        for (Map.Entry<String, String> entry : map.entrySet()) {
            arrMap.add(entry.getKey() + "=" + entry.getValue());
        }
        String params = String.join("&", arrMap);
        String hash = DigestUtils.md5Hex(bytes);
        return DigestUtils.md5Hex(hash + "/" + params);
    }



}
